/*=========================================================================
 *
 *  Copyright NumFOCUS
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         https://www.apache.org/licenses/LICENSE-2.0.txt
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *
 *=========================================================================*/
/*=========================================================================
 *
 *  Portions of this file are subject to the VTK Toolkit Version 3 copyright.
 *
 *  Copyright (c) Ken Martin, Will Schroeder, Bill Lorensen
 *
 *  For complete copyright, license and disclaimer of warranty information
 *  please refer to the NOTICE file at the top of the ITK source tree.
 *
 *=========================================================================*/
#ifndef itkImageToImageFilter_hxx
#define itkImageToImageFilter_hxx
#include "itkInputDataObjectIterator.h"
#include "itkInputDataObjectConstIterator.h"

#include <cmath>

namespace itk
{

template <typename TInputImage, typename TOutputImage>
ImageToImageFilter<TInputImage, TOutputImage>::ImageToImageFilter()
  : m_CoordinateTolerance(Self::GetGlobalDefaultCoordinateTolerance())
  , m_DirectionTolerance(Self::GetGlobalDefaultDirectionTolerance())
{
  // Modify superclass default values, can be overridden by subclasses
  this->SetNumberOfRequiredInputs(1);
}

template <typename TInputImage, typename TOutputImage>
void
ImageToImageFilter<TInputImage, TOutputImage>::SetInput(const InputImageType * input)
{
  // Process object is not const-correct so the const_cast is required here
  this->ProcessObject::SetNthInput(0, const_cast<InputImageType *>(input));
}


template <typename TInputImage, typename TOutputImage>
void
ImageToImageFilter<TInputImage, TOutputImage>::SetInput(unsigned int index, const TInputImage * image)
{
  // Process object is not const-correct so the const_cast is required here
  this->ProcessObject::SetNthInput(index, const_cast<TInputImage *>(image));
}


template <typename TInputImage, typename TOutputImage>
auto
ImageToImageFilter<TInputImage, TOutputImage>::GetInput() const -> const InputImageType *
{
  return itkDynamicCastInDebugMode<const TInputImage *>(this->GetPrimaryInput());
}


template <typename TInputImage, typename TOutputImage>
auto
ImageToImageFilter<TInputImage, TOutputImage>::GetInput(unsigned int idx) const -> const InputImageType *
{
  const auto * in = dynamic_cast<const TInputImage *>(this->ProcessObject::GetInput(idx));

  if (in == nullptr && this->ProcessObject::GetInput(idx) != nullptr)
  {
    itkWarningMacro("Unable to convert input number " << idx << " to type " << typeid(InputImageType).name());
  }
  return in;
}


template <typename TInputImage, typename TOutputImage>
void
ImageToImageFilter<TInputImage, TOutputImage>::GenerateInputRequestedRegion()
{
  Superclass::GenerateInputRequestedRegion();

  for (InputDataObjectIterator it(this); !it.IsAtEnd(); ++it)
  {
    // Check whether the input is an image of the appropriate dimension
    using ImageBaseType = ImageBase<InputImageDimension>;
    auto * input = dynamic_cast<ImageBaseType *>(it.GetInput());
    if (input)
    {
      // Use the function object RegionCopier to copy the output region
      // to the input.  The default region copier has default implementations
      // to handle the cases where the input and output are the same
      // dimension, the input a higher dimension than the output, and the
      // input a lower dimension than the output.
      typename ImageBaseType::RegionType inputRegion;
      this->CallCopyOutputRegionToInputRegion(inputRegion, this->GetOutput()->GetRequestedRegion());
      input->SetRequestedRegion(inputRegion);
    }
  }
}


template <typename TInputImage, typename TOutputImage>
void
ImageToImageFilter<TInputImage, TOutputImage>::CallCopyOutputRegionToInputRegion(
  InputImageRegionType &        destRegion,
  const OutputImageRegionType & srcRegion)
{
  const OutputToInputRegionCopierType regionCopier;

  regionCopier(destRegion, srcRegion);
}


template <typename TInputImage, typename TOutputImage>
void
ImageToImageFilter<TInputImage, TOutputImage>::CallCopyInputRegionToOutputRegion(OutputImageRegionType & destRegion,
                                                                                 const InputImageRegionType & srcRegion)
{
  const InputToOutputRegionCopierType regionCopier;

  regionCopier(destRegion, srcRegion);
}


template <typename TInputImage, typename TOutputImage>
void
ImageToImageFilter<TInputImage, TOutputImage>::PrintSelf(std::ostream & os, Indent indent) const
{
  Superclass::PrintSelf(os, indent);
  os << indent << "CoordinateTolerance: " << this->m_CoordinateTolerance << std::endl;
  os << indent << "DirectionTolerance: " << this->m_DirectionTolerance << std::endl;
}


template <typename TInputImage, typename TOutputImage>
void
ImageToImageFilter<TInputImage, TOutputImage>::VerifyInputInformation() const
{

  using ImageBaseType = const ImageBase<InputImageDimension>;

  ImageBaseType *              inputPtr1 = nullptr;
  InputDataObjectConstIterator it(this);

  for (; !it.IsAtEnd(); ++it)
  {
    // Check whether the output is an image of the appropriate
    // dimension (use ProcessObject's version of the GetInput()
    // method since it returns the input as a pointer to a
    // DataObject as opposed to the subclass version which
    // static_casts the input to an TInputImage).
    inputPtr1 = dynamic_cast<ImageBaseType *>(it.GetInput());

    if (inputPtr1)
    {
      break;
    }
  }

  for (; !it.IsAtEnd(); ++it)
  {
    auto * inputPtrN = dynamic_cast<ImageBaseType *>(it.GetInput());

    // Physical space computation only matters if we're using two
    // images, and not an image and a constant.
    if (inputPtrN)
    {
      // check that the image occupy the same physical space, and that
      // each index is at the same physical location

      // tolerance for origin and spacing depends on the size of pixel
      // tolerance for directions a fraction of the unit cube.
      const SpacePrecisionType coordinateTol =
        itk::Math::abs(this->m_CoordinateTolerance * inputPtr1->GetSpacing()[0]); // use first dimension spacing

      if (!inputPtr1->IsCongruentImageGeometry(inputPtrN, m_CoordinateTolerance, m_DirectionTolerance))
      {
        std::ostringstream originString;
        if (!inputPtr1->GetOrigin().GetVnlVector().is_equal(inputPtrN->GetOrigin().GetVnlVector(), coordinateTol))
        {
          originString.setf(std::ios::scientific);
          originString.precision(7);
          originString << "InputImage Origin: " << inputPtr1->GetOrigin() << ", InputImage " << it.GetName()
                       << " Origin: " << inputPtrN->GetOrigin() << std::endl;
          originString << "\tTolerance: " << coordinateTol << std::endl;
        }
        std::ostringstream spacingString;
        if (!inputPtr1->GetSpacing().GetVnlVector().is_equal(inputPtrN->GetSpacing().GetVnlVector(), coordinateTol))
        {
          spacingString.setf(std::ios::scientific);
          spacingString.precision(7);
          spacingString << "InputImage Spacing: " << inputPtr1->GetSpacing() << ", InputImage " << it.GetName()
                        << " Spacing: " << inputPtrN->GetSpacing() << std::endl;
          spacingString << "\tTolerance: " << coordinateTol << std::endl;
        }
        std::ostringstream directionString;
        if (!inputPtr1->GetDirection().GetVnlMatrix().is_equal(inputPtrN->GetDirection().GetVnlMatrix(),
                                                               this->m_DirectionTolerance))
        {
          directionString.setf(std::ios::scientific);
          directionString.precision(7);
          directionString << "InputImage Direction: " << inputPtr1->GetDirection() << ", InputImage " << it.GetName()
                          << " Direction: " << inputPtrN->GetDirection() << std::endl;
          directionString << "\tTolerance: " << this->m_DirectionTolerance << std::endl;
        }
        itkExceptionMacro("Inputs do not occupy the same physical space! " << std::endl
                                                                           << originString.str() << spacingString.str()
                                                                           << directionString.str());
      }
    }
  }
}


template <typename TInputImage, typename TOutputImage>
void
ImageToImageFilter<TInputImage, TOutputImage>::PushBackInput(const InputImageType * input)
{
  // Forward to the protected method in the superclass
  this->ProcessObject::PushBackInput(input);
}


template <typename TInputImage, typename TOutputImage>
void
ImageToImageFilter<TInputImage, TOutputImage>::PopBackInput()
{
  // Forward to the protected method in the superclass
  this->ProcessObject::PopBackInput();
}


template <typename TInputImage, typename TOutputImage>
void
ImageToImageFilter<TInputImage, TOutputImage>::PushFrontInput(const InputImageType * input)
{
  // Forward to the protected method in the superclass
  this->ProcessObject::PushFrontInput(input);
}


template <typename TInputImage, typename TOutputImage>
void
ImageToImageFilter<TInputImage, TOutputImage>::PopFrontInput()
{
  // Forward to the protected method in the superclass
  this->ProcessObject::PopFrontInput();
}

} // end namespace itk

#endif
